package de.tum.in.far.threedui.superbrickball.engine;

import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;

import javax.media.j3d.Transform3D;
import javax.vecmath.Vector3f;

import de.tum.in.far.threedui.general.PoseListener;
import de.tum.in.far.threedui.superbrickball.engine.physics.CollisionDetector;
import de.tum.in.far.threedui.superbrickball.engine.physics.Nature;
import de.tum.in.far.threedui.superbrickball.engine.physics.World;
import de.tum.in.far.threedui.superbrickball.engine.physics.models.PhysicalModel;
import de.tum.in.far.threedui.superbrickball.engine.physics.models.PlaneModel;
import de.tum.in.far.threedui.superbrickball.engine.physics.models.SphereModel;
import de.tum.in.far.threedui.superbrickball.gui.SuperBallGame;

public class GameController implements ClockListener, PoseListener {
	SuperBallGame gameGUIInstance;
	World gameWorldInstance;
	Map<String, Transform3D> poseMap;
	GAME_STATE gameState;
	ClockGenerator gameClock;

	enum GAME_STATE {
		GAME_WON, GAME_LOST, GAME_STARTED, GAME_CREATED,
	}

	public GameController(World world) {
		this.gameGUIInstance = null;

		this.gameWorldInstance = world;
		this.poseMap = Collections
				.synchronizedMap(new Hashtable<String, Transform3D>());

		this.gameClock = new ClockGenerator(world.CLOCK_PERIOD);
		this.gameClock.addClockListener(this);

		this.gameState = GAME_STATE.GAME_CREATED;

	}

	public void setGameGUI(SuperBallGame gameGUI) {
		this.gameGUIInstance = gameGUI;
		createScene();
	}

	private void createScene() {
		for (Entry<String, PhysicalModel> targetEntry : this.gameWorldInstance.targetModels
				.entrySet()) {
			this.gameGUIInstance.addSphericalBrick(targetEntry.getKey(),
					((SphereModel) targetEntry.getValue()).getTransform3D(),
					((SphereModel) targetEntry.getValue()).getRadius());
		}

		float wallThickness = 0.0001f;

		for (Entry<String, PlaneModel> wallEntry : this.gameWorldInstance.wallModels
				.entrySet()) {

			if (wallEntry.getKey().equals(("T"))
					|| wallEntry.getKey().equals(("D"))) {
				float xDim = this.gameWorldInstance.width;
				float yDim = this.gameWorldInstance.depth;
				float zDim = wallThickness;

				this.gameGUIInstance.addWall(wallEntry.getValue()
						.getTransform(), xDim, yDim, zDim);
			}

			else if (wallEntry.getKey().equals(("R"))
					|| wallEntry.getKey().equals(("L"))) {
				float xDim = this.gameWorldInstance.height;
				float yDim = this.gameWorldInstance.depth;
				float zDim = wallThickness;
				this.gameGUIInstance.addWall(wallEntry.getValue()
						.getTransform(), xDim, yDim, zDim);
			}

			else if (wallEntry.getKey().equals(("F"))) {
				float xDim = this.gameWorldInstance.width;
				float yDim = this.gameWorldInstance.height;
				float zDim = wallThickness;
				this.gameGUIInstance.addWall(wallEntry.getValue()
						.getTransform(), xDim, yDim, zDim);
			}
		}
	}

	public void startGame() {
		this.gameState = GAME_STATE.GAME_STARTED;
		this.gameClock.start();
	}

	@Override
	public void onClockSignal(long virtualTime, long period) {
		updateGameState();
	}

	private void updateGameState() {

		if (this.gameState == GAME_STATE.GAME_CREATED)
			return;
		if (this.gameState == GAME_STATE.GAME_LOST)
			return;
		if (this.gameState == GAME_STATE.GAME_WON)
			return;

		if (checkWinning()) {
			this.gameState = GAME_STATE.GAME_WON;
			this.gameGUIInstance.endGame(true);
			return;
		}

		if (checkLosing()) {
			this.gameState = GAME_STATE.GAME_LOST;
			this.gameGUIInstance.endGame(false);
			return;
		}

		// check for collision with walls
		for (PlaneModel wall : this.gameWorldInstance.wallModels.values()) {
			if (CollisionDetector
					.isColliding(gameWorldInstance.ballModel, wall, gameWorldInstance.ballVelocity.length())) {
				handleWallCollision(wall);
			}
		}

//		 check for collision with targets
		for (Entry<String, PhysicalModel> ballEntry : this.gameWorldInstance.targetModels.entrySet()) {
			if (CollisionDetector.isColliding(gameWorldInstance.ballModel,
					ballEntry.getValue(), gameWorldInstance.ballVelocity.length())) {
				handleTargetCollision(ballEntry.getKey(), ballEntry.getValue());
				break;
			}
		}

		// check for force
		if (this.poseMap.containsKey("CONTROLLER")) {
			Vector3f magneticForce = Nature.getForce(this.poseMap.get("CONTROLLER"),
					this.gameWorldInstance.REPULSION,
					this.gameWorldInstance.ballModel.getPosition());
			
			if (magneticForce.length() >= this.gameWorldInstance.MIN_FORCE_THRESHOLD) {
				Nature.affect(magneticForce,
						this.gameWorldInstance.ballVelocity);
			}
		}
		
		
		//check maximum velocity constraint
		if(this.gameWorldInstance.ballVelocity.length() > this.gameWorldInstance.MAX_SPEED)
		{
			this.gameWorldInstance.ballVelocity.normalize();
			this.gameWorldInstance.ballVelocity.scale(this.gameWorldInstance.MAX_SPEED);
		}

		// move ball
		Nature.move(this.gameWorldInstance.ballVelocity,
				this.gameWorldInstance.ballModel.getPosition());

		// update GUI
		this.gameGUIInstance.setBallTransform(this.gameWorldInstance.ballModel
				.getTransform3D());

	}

	private boolean checkLosing() {

		return this.gameState == GAME_STATE.GAME_LOST
				|| CollisionDetector.isColliding(
						this.gameWorldInstance.ballModel,
						this.gameWorldInstance.wallModels.get("B"), gameWorldInstance.ballVelocity.length());
	}

	private boolean checkWinning() {
		return this.gameWorldInstance.targetModels.isEmpty();
	}

	private void handleTargetCollision(String targetID, PhysicalModel target) {
		if (target instanceof SphereModel) {
			Vector3f ballVelocity = this.gameWorldInstance.ballVelocity;
			Vector3f ballPosition = this.gameWorldInstance.ballModel
					.getPosition();
			Vector3f targetPosition = ((SphereModel) target).getPosition();

			Vector3f collisionDirection = new Vector3f(ballPosition);
			collisionDirection.sub(targetPosition);

			Nature.collide(collisionDirection, ballVelocity);

			destroy(targetID);
		}

	}

	private void destroy(String targetID) {
		this.gameWorldInstance.targetModels.remove(targetID);
		this.gameGUIInstance.destroyBrick(targetID);
	}

	private void handleWallCollision(PlaneModel wall) {
		Nature.collide(wall.getNormal(), this.gameWorldInstance.ballVelocity);
	}

	@Override
	public void onPoseReceived(String message, Transform3D t3d) {
		this.poseMap.put(message, t3d);
	}

}
